#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "ktota.h"
#include "ktota_master.h"

#if 1
#define master_dbg	printf
#else
#define master_dbg(...) do{}while(0)
#endif

#define ERR_MIGIC_NOT_MATCH "magic numb do not match"
#define ERR_VERSION_NOT_MATCH "version numb do not match"
#define ERR_OTHER "other err"

#define OTA_FILE_SIZE       (128*1024)

static const uint8_t *firmware_buf = NULL;
static uint32_t	master_send_idx = 0; /* 主机发送序号，也是启动/停止的标志 */
static uint32_t slave_ack_idx = 0; /* 从机回应的序号 */
static uint8_t farme_pack_buf[KTOTA_MAX_PACK_LEN];

static volatile uint32_t retry_tick = 0;
static volatile uint32_t disconnect_tick = 0;
#define _OTA_RETRY_CAP(u_ms) app_capture_interval(&retry_tick, u_ms)
#define _OTA_RETRY_UPDATE() app_update_interval(&retry_tick)
#define _OTA_DISCONNCT_CAP(u_ms) app_capture_interval(&disconnect_tick, u_ms)
#define _OTA_DISCONNCT_UPDATE() app_update_interval(&disconnect_tick)

static uint8_t _ota_data_check(const uint8_t* databuf, uint32_t datalen)
{
	uint32_t cal_val;
	bin_head *pdata= (bin_head*)databuf;
	if(datalen < BIN_HEAD_LEN){
		printf("firmware len < BIN_HEAD_LEN \n");
		return 0;
	}

	if(datalen != pdata->head_len + pdata->data_len){
		printf("firmware len do not match head description \n");
		return 0;
	}
	
	if(pdata->model != BOARD_MODEL){
		printf("firmware magic number (board model) don't match \n");
		return 0;
	}
	
	cal_val = ota_calcrc32(databuf+ pdata->head_len, pdata->data_len);
	if(cal_val != pdata->crc32val){
		printf("firmware crc32val= %08X, but calculate val= %08X \n", cal_val, pdata->crc32val);
		return 0;
	}
    master_dbg("ota file model: %08X ,",pdata->model);
	master_dbg("version :%x ,",pdata->version);
	master_dbg("data len :%d ,",pdata->data_len);
	master_dbg("crc32val :%08X\n",pdata->crc32val);
	return 1;
}

static uint32_t _ota_pack_frame(const uint8_t* databuf, uint8_t* packbuf)
{	
	uint32_t len;
	bin_head *pdata= (bin_head*)databuf;
	KTOTA_REGION *firmware= (KTOTA_REGION*)(packbuf);
	firmware->model = pdata->model;
	firmware->version = pdata->version;
	firmware->file_len = pdata->data_len+ BIN_HEAD_LEN;
	firmware->flash_bak_base = 0; /* slave并不使用 */
	len= firmware->file_len - firmware->region_idx;
	if(len > KTOTA_MAX_LOAD_LEN) {
		len = KTOTA_MAX_LOAD_LEN;
	}
	if(firmware->region_idx < OTA_FILE_SIZE) {
		memcpy((uint8_t*)firmware->flash_data, databuf+firmware->region_idx, len);
	}else {
		printf("region idx too long\n");
	}	
	firmware->crc32val= ota_calcrc32((uint8_t*)firmware->flash_data, len);
	firmware->data_len= len;
	return len;
}

static uint32_t _ota_read_slave_ack()
{
    uint32_t ret = slave_ack_idx;
    char *err = NULL;
    switch(ret) {
    case OTAMSGERR_MODEL:
        err = ERR_MIGIC_NOT_MATCH;
        break;
    case OTAMSGERR_VERSION:
        err = ERR_VERSION_NOT_MATCH;
        break;
    case OTAMSGERR_OTHER:
        err = ERR_OTHER;
        break;
    default:
        break;
    }
    if(err) {
        master_dbg("slave ack %s\r\n", err);
    }
	return ret;
}

static void _ota_update_timer(void)
{
	_OTA_RETRY_UPDATE();
	_OTA_DISCONNCT_UPDATE();
}

int ktota_master_file_check(const char *filename)
{
    int fd, filelen;
    struct stat sb;
    char *mapped;
    /* 打开文件 */
    if ((fd = open (filename, O_RDWR)) < 0) {
        printf("no ota update file\n");
        return -1;
    }
    /* 获取文件的属性 */
    if ((fstat (fd, &sb)) == -1) {
        printf("ota update file fstat err\n");
        return -1;
    }
    filelen = sb.st_size;

    /* 将文件映射至进程的地址空间 */
    if ((mapped = (char *) mmap (NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == (void *) -1)
    {
        printf("ota update file mmap err\n");
    }
    /* 映射完后, 关闭文件也可以操纵内存 */
    close(fd);
    printf("ota update file size: %d bytes\n", filelen);
    /* 修改一个字符,同步到磁盘文件 */
    if(1 == _ota_data_check((const uint8_t*)mapped, filelen)) {
        firmware_buf = (const uint8_t*)mapped;
        return 0;
    }else {
        printf("ota update file check faild\n");
        return -2;
    } 
}

void ktota_master_parse_ack(uint8_t *buf, uint32_t len)
{
    uint32_t num = 0;
    if(len == 4) {
        num|= (uint32_t)buf[0];
        num|= (uint32_t)buf[1] << 8;
        num|= (uint32_t)buf[2] << 16;
        num|= (uint32_t)buf[3] << 24;
        slave_ack_idx = num;	
    }
}

uint8_t ktota_master_serve_loop(void (*cb)(uint8_t*, uint32_t))
{
	uint32_t len, tlen, ack_idx;
	uint8_t ret = 1;
	const bin_head *pdata= (const bin_head*)firmware_buf;
	KTOTA_REGION *pack= (KTOTA_REGION*)(farme_pack_buf);
	if(master_send_idx) {
		tlen= master_send_idx;
		ack_idx = _ota_read_slave_ack();
        if(ack_idx >= OTAMSGERR_OTHER) {
            ktota_master_stop();
            return 0;
        }
		if(ack_idx < tlen) {
			/*获取从机已接收的字节数（序号）*/
			if(ack_idx == pack->region_idx + pack->data_len){
				pack->region_idx+= pack->data_len;
				_ota_update_timer();
			} else {
				if(1 == _OTA_DISCONNCT_CAP(OTA_DISCONNCT_TIME)){
					printf("err, firmware send time out: %d/ %d\n",ack_idx, pack->region_idx + pack->data_len);
					ktota_master_stop();
					return 0;
				}
				if(1 == _OTA_RETRY_CAP(OTA_RETRY_TIME)){
					printf("firmware pend: %d/ %d,  %d %d resend\n",ack_idx, pack->region_idx + pack->data_len,
					            pack->region_idx, pack->data_len);
					_OTA_RETRY_UPDATE();	
				}
				else {
					return 0;	
				}
			}			
			/*从机序号已更新立即发，或者超时重发*/
			len = _ota_pack_frame((uint8_t*)pdata, farme_pack_buf);
            master_dbg("send idx %d / %d\n", ack_idx, pack->region_idx + pack->data_len);
            cb(farme_pack_buf, len + KTOTA_HEAD_LEN);
            ret = 1; /* flag send msg */
		}
		else {
			if(ack_idx == master_send_idx) {
				master_dbg("firmware send ok\r\n");
			}
			ktota_master_stop();
		}
	}
	return ret;
}

void ktota_master_start(void) {
    bin_head *pdata = (bin_head*)firmware_buf;
	slave_ack_idx = 0;
	memset(farme_pack_buf, 0, KTOTA_HEAD_LEN);/* 只在这里清空pack的头部 */
	master_send_idx= pdata->head_len + pdata->data_len; /* 整个升级文件的长度  */
	_ota_pack_frame(firmware_buf, farme_pack_buf); /* 先组好第一帧 */
	_ota_update_timer();
}

void ktota_master_stop(void) {
    master_send_idx = 0;
}

void ktota_master_close(void) {
    if(firmware_buf != NULL) {
        if ((munmap ((void *) firmware_buf, master_send_idx)) == -1) {
            printf("ota file munmap err\n");
        }
    }
    firmware_buf = NULL;
    master_send_idx = 0;
}




